package server;

import static server.EventManager.AUTHENTICATED;
import static server.EventManager.CONNECTED;
import static shared.Status.CLOSE_STREAM;
import static shared.Status.KILLED;
import static shared.Status.OPEN_STREAM;
import static shared.Status.RECEIVE;
import static shared.StatusHandler.updateStatus;

import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Document;

import shared.CoordObject;
import shared.HandlerThread;
import shared.TaskObject;
import shared.XMLModem;
import client.ClientEvent;
import client.ClientListener;

public class ClientHandler extends HandlerThread implements ClientListener
{
	
	private int handlerId;
	public static int currentConnections = 0;
	private static int nextAssignment = 0;
	private boolean authenticated = false;
	private Vector<ClientObject> clientList; // List for keeping track of clients/threads
	private EventManager manager = EventManager.getInstance();
	private XMLModem modem = new XMLModem();
	
	public ClientHandler(Socket toHandle)
	{
		this.connection = toHandle;
	}
	
	public ClientHandler(Socket toHandle,Vector<ClientObject> clientList)
	{
		this(toHandle);
		this.clientList = clientList;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		assignID();
		updateStatus(OPEN_STREAM);
		openStreams();
		handle();
		updateStatus(CLOSE_STREAM);
		stopStreams();
	}
	
	public void killConnection()
	{
		if(connection == null || connection.isClosed())
		{
			stopStreams();
			
			if(connection != null) updateStatus(KILLED," "+handlerId+" "+connection.getInetAddress());
			
			connection = null;
		}
	}
	
	/**
	 * Assign a unique ID to handler
	 */
	private void assignID()
	{
		this.handlerId = nextAssignment++;
		currentConnections++;
	}
	
	
	
	/**
	 * Handle the connection
	 */
	private void handle()
	{
		while(connection.isConnected())
		{
			if(canReceive())
			{
				try 
				{
					if(authenticated)
					{
						Document doc = (Document) streamIn.readObject();
						updateStatus(RECEIVE,doc.toString());
						modem.parseDocument(doc);						
					}
					else
					{
						String message = (String) streamIn.readObject();
						updateStatus(RECEIVE,message);
						scanTextMessage(message); // REM
						sendConfirmation();						
						Thread.sleep(1000);
						
						TaskObject tos = new TaskObject();						
						CoordObject cord = new CoordObject(15, 18, "Sefverl");
						tos.addCoordObject(cord);						
						Document toSend = modem.generateDocument(tos);
						sendObject(toSend);
					}
					
					
					
					// TODO: Distribute message
				}
				catch (IOException e)
				{
					// TODO Auto-generated catch block
					// TODO: Delegate to a Error Handler
					
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			else
			{
				updateStatus(CLOSE_STREAM);
				stopStreams();
			}
		}
	}	
	
	/********************************************************************
	 * Methods for authentication, validation of client and its messages
	 *******************************************************************/
	
	/* This method requires debugging and testing*/
	private void scanTextMessage(String message)
	{
		print("Starting to validate ...."); // REM
		if(message.contains("@^Auth@")) 
		{
			ClientObject object = new ClientObject();
			Pattern pattern = Pattern.compile("~.+~");
			Matcher match = pattern.matcher(message);
			
			print(match); // REM
			if(match.find())
			{
				String infos = match.group(0);
				print(infos); // REM
				infos = infos.substring(1, infos.length()-2);
				print(infos); // REM
				String[] key_value_pairs = infos.split(";");
				print(key_value_pairs); // REM
				
				for(int i = 0; i<key_value_pairs.length; i++)
				{
					String[] key_value = key_value_pairs[i].split(":");
					
					if(key_value[0].equalsIgnoreCase("nm"))
					{
						object.setClientName(key_value[1]);
						print(key_value[1]); // REM
						// TODO: Generate name if client hasn't got a name yet (e.g. client007)
					}
					else if(key_value[0].equalsIgnoreCase("vs"))
					{
						object.setVersion(Double.parseDouble(key_value[1]));
						print(key_value[1]); // REM
						// TODO: Force client to have certain version / kick if not compliant
					}
				}
				
				// Store information about the new client in global database
				object.setAddress(connection.getInetAddress());
				object.setClientID(handlerId);
				object.setHandler(this);
				clientList.add(object);
				
				// Throw event about new client
				registerEvent(CONNECTED, object);
				authenticated = true; // Might add password system				
				registerEvent(AUTHENTICATED, object);
				
				System.out.println(object);
			}
		}
	}
	
	private void print(Object message)
	{
		System.out.println(message);
	}
	
	private void sendConfirmation()
	{
		String toSend = "";
		
		if(authenticated)
		{
			toSend = "@^Auth@_~status:AUTHENTICATED;~";
		}
		else
		{
			toSend = "@^Auth@_~status:ACCESS DENIED;~";
		}
		
		send(toSend);
	}
	
	private void registerEvent(int type,ClientObject object)
	{
		print("Called me @ RegisterEvent");
		manager.fireEvent(this,object, type);
	}
	
	/*******************************************************************
	 * Event Listener Methods
	 ******************************************************************/

	// TODO: Implement ClientListener methods or delegate to ClientListenerAdapter
	
	/* Listen to other threads and receive events. Use these events to notify the clients that are being handled.
	 * 
	 */
	@Override
	public void clientConnected(ClientEvent e) {
		// TODO Auto-generated method stub
		System.out.println("Client connected");
	}

	@Override
	public void clientDisconnected(ClientEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void ClientEventPerformed(ClientEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void clientAuthenticated(ClientEvent e) {
		// TODO Auto-generated method stub
		ClientObject client = e.getClient();
		
		send(client.getClientName()+"("+client.getClientID()+") has joined the conversation");
	}
}
